/* Complet (Autocompletion through Graph Databases)
 *  Copyright (C) 2012  Paul Wagner, Till Speicher
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, or (at your
 *  option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http:/* www.gnu.org/licenses/>.
 */

/*
 * Class for performing lookups on the database.
 * Give sentence using setSentence()
 * Make lookup using eval()
 * See result using getResult() or toString()
 *
 * @ author Paul Wagner, René Pickhardt
 */
package me.test.neo4j;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.IndexHits;

public class Retrieval extends DBConn {

    // PROPERTIES
    private Integer[] p;
    private String[] words;
    private String offset;
    private TreeMap<String, Word> res;
    private TreeMap<String, Word> res_relloc;
    private TreeMap<String, Word> res_prmCnt;
    private String NAME_KEY = ConfigHelper.NAME_KEY;
    private String COUNT_KEY = ConfigHelper.COUNT_KEY;
    private String REL_KEY = ConfigHelper.RELLOC_KEY;
    private boolean FULL_LIST = false;
    //COMPARATORS
    private final Comparator<String> COMP_REL = new Comparator<String>() {

        public int compare(String s1, String s2) {
            //return res.get(s2).getAVG_RELLOC().compareTo(res.get(s1).getAVG_RELLOC());
            if (res.get(s1).getAVG_REL() > res.get(s2).getAVG_REL()) {
                return -1;
            }
            if (res.get(s1).getAVG_REL() < res.get(s2).getAVG_REL()) {
                return 1;
            }
            int _return = res.get(s1).getWORD().compareTo(res.get(s2).getWORD());
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };
    private final Comparator<String> COMP_CNT = new Comparator<String>() {

        public int compare(String s1, String s2) {
            //return res.get(s2).getAVG_RELLOC().compareTo(res.get(s1).getAVG_RELLOC());
            if (res.get(s1).getPRM_COUNT() > res.get(s2).getPRM_COUNT()) {
                return -1;
            }
            if (res.get(s1).getPRM_COUNT() < res.get(s2).getPRM_COUNT()) {
                return 1;
            }
            int _return = res.get(s1).getWORD().compareTo(res.get(s2).getWORD());
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };

    /*
     * Constructor using ConfigHelper
     */
    public Retrieval() {
        this(ConfigHelper.DB_PATH, ConfigHelper.MAX_RELATIONS, ConfigHelper.NAME_KEY, ConfigHelper.COUNT_KEY, ConfigHelper.CACHE_TYPE, ConfigHelper.BUFFER);
    }

    /*
     * Constructor using manual config values
     *
     */
    public Retrieval(String database, int max_relations, String name_key, String count_key, String cache_type, Boolean buffer) {
        super(database, max_relations, name_key, cache_type, buffer);
        this.NAME_KEY = name_key;
        this.COUNT_KEY = count_key;
        this.p = new Integer[4];

        // Instanciate TreeMaps with different comparators
        this.res = new TreeMap<String, Word>();
        this.res_relloc = new TreeMap<String, Word>(COMP_REL);
        this.res_prmCnt = new TreeMap<String, Word>(COMP_CNT);
    }

    // GETTER AND SETTER
    public Integer getP1() {
        return p[0];
    }

    public void setP1(Integer p1) {
        this.p[0] = p1;
    }

    public Integer getP2() {
        return p[1];
    }

    public void setP2(Integer p2) {
        this.p[1] = p2;
    }

    public Integer getP3() {
        return p[2];
    }

    public void setP3(Integer p3) {
        this.p[2] = p3;
    }

    public Integer getP4() {
        return p[3];
    }

    public void setP4(Integer p4) {
        this.p[3] = p4;
    }

    public String getCOUNT_KEY() {
        return COUNT_KEY;
    }

    public String getNAME_KEY() {
        return NAME_KEY;
    }

    public boolean isFULL_LIST() {
        return FULL_LIST;
    }

    public void setFULL_LIST(boolean FULL_LIST) {
        this.FULL_LIST = FULL_LIST;
    }

    // METHODS

    /*
     * Set sentence before performing lookup
     * Please be aware of the fact, that your sentence shouldn't be longer than your DB provides!
     *
     * @param words Words in the sentence
     * @param offest Letters known from the current word
     */
    public void setSentence(String[] words, String offset) {
        this.words = words;
        if (this.words.length > 4) {
            this.words = null;
            IOHelper.log("(Retrieval) WARNING: setSentence() Wordlength out of bounds!");
        }
        this.offset = offset;
    }

    /*
     * Perfoms primitive lookup
     *
     * @return lookuptime Evaluating time in ms
     */
    public double primitiveEval() {
        long start_time = System.nanoTime();
        this.res.clear();
        this.res_prmCnt.clear();

        try {
            IndexHits<Node> hits = getWordIndex().query(this.NAME_KEY, this.offset + "*");
            for (Node node : hits) {
                int cnt_prm = (Integer) node.getProperty(COUNT_KEY);
                String tmp = (String) node.getProperty(NAME_KEY);
                Word new_word = new Word(node, tmp, 0, 0.0, cnt_prm);
                this.res.put(tmp, new_word);
            }

        } catch (Exception e) {
            IOHelper.logError("(Retrieval.primitiveEval()) " + e.getMessage());
            return -1;
        }

        fillPrimitiveSortedResultSets();
        long end_time = System.nanoTime();
        return (double) (Math.round((double) (end_time - start_time) / 1000)) / 1000;
    }

    /*
     * Perfoms lookup
     *
     * @return lookuptime Evaluating time in ms
     */
    public double eval() {
        long start_time = System.nanoTime();
        this.res.clear();
        this.res_relloc.clear();

        try {
            int len = this.words.length;
            Node startNode = null;
            for (int i = 0; i < len; i++) {
                startNode = getWordIndex().get(this.NAME_KEY, this.words[i]).getSingle();
                if (startNode == null) {
                    continue;
                }

                if(this.p[len - i - 1] == 0) continue; // Saves nonsense-lookups

                for (Relationship rel : startNode.getRelationships(getDn()[len - i], Direction.OUTGOING)) {
                    int cnt = 0;
                    int c_cnt = 0;
                    double relloc = 0;
                    double c_relloc = 0;

                    String tmp = (String) rel.getEndNode().getProperty(this.NAME_KEY);
                    if (this.FULL_LIST || tmp.startsWith(this.offset)) {
                        if (this.res.containsKey(tmp)) {
                            cnt = this.res.get(tmp).getSUM_COUNT();
                            relloc = this.res.get(tmp).getAVG_REL();
                        }
                        c_cnt = 0;
                        c_relloc = 0;
                        c_cnt = (Integer) rel.getProperty(COUNT_KEY);
                        if (rel.hasProperty(REL_KEY)) {
                            c_relloc = (Double) rel.getProperty(REL_KEY);
                        }

                        Word new_word = new Word(rel.getEndNode(), tmp, cnt + this.p[len - i - 1] * c_cnt, relloc + this.p[len - i - 1] * c_relloc, 0);
                        this.res.put(tmp, new_word);
                    }
                }
            }
        } catch (Exception e) {
            IOHelper.logError("(Retrieval.eval()) " + e.getMessage());
            return -1;
        }
        fillSortedResultSets();
        long end_time = System.nanoTime();
        return (double) (Math.round((double) (end_time - start_time) / 1000)) / 1000;
    }

    /*
     * Returns unordered resultset
     *
     */
    public TreeMap<String, Word> getResult() {
        return this.res;
    }

    /*
     * Returns ordered keyset to iterate over resultset
     *
     */
    public Set<String> getSortedKeySet(Integer sort) {
        switch (sort) {
            case 1:
                return this.res_relloc.keySet();
            case 3:
                return this.res_prmCnt.keySet();
            default:
                return null;
        }
    }

    /*
     * toString methode
     * Gives results as a stringF
     *
     * @param sort 0: result sorted by count, 1: result sorted by loc norm, 2: result sorted by glob norm
     */
    @Override
    public String toString() {
        return this.toString(0);
    }

    public String toString(Integer sort) {
        StringBuilder sb = new StringBuilder();
        Set<String> set = getSortedKeySet(sort);
        if (set == null || set.isEmpty()) {
            return "No element found!";
        }
        for (String key : set) {
            sb.append(key);
            sb.append(" : ");
            switch (sort) {
                case 0:
                    sb.append(res.get(key).getSUM_COUNT());
                    break;
                case 1:
                    sb.append(res.get(key).getAVG_REL());
                    break;
                case 3:
                    sb.append(res.get(key).getPRM_COUNT());
                    break;
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    private void fillSortedResultSets() {
        this.res_relloc.putAll(res);
    }

    private void fillPrimitiveSortedResultSets() {
        this.res_prmCnt.putAll(res);
    }
}
